// ==UserScript== // @name 哔哩哔哩番剧助手 | Bilibili Bangumi Assistant // @namespace http://tampermonkey.net/ // @version 3.3 // @description 自动跳过片头片尾,支持自定义快进秒数和播放速度记忆功能,使观看体验更加流畅。 // @description-en Automatically skip opening and ending sequences, support customizable fast-forward seconds and playback speed memory for a smoother viewing experience. // @author MarySue // @match *://*.bilibili.com/bangumi/play/ep* // @grant none // @run-at document-end // @license GNU GPLv3 // ==/UserScript== (function() { 'use strict'; // 用户自定义块开始 ----------------------------------------------- const userSettings = { settings: [ { pattern: '^https?://(www\\.)?bilibili\\.com/bangumi/play/ep\\d+', // 匹配带有可变ep编号的URL titlePattern: /柯南/, // 包含"柯南" introDuration: 99, // 片头时长 (秒) outroDuration: 0, // 片尾时长 (秒) forwardSeconds: 80, // 快进秒数 forwardKey: 'KeyK', // 快进快捷键 playbackSpeedKeys: { slowDown: 'KeyZ', // 慢速热键 reset: 'KeyX', // 重置速度热键 speedUp: 'KeyC' // 快速热键 }, playbackSpeedSteps: { decreaseStep: 0.1, // 每次按下慢速热键减少的速度 increaseStep: 0.1 // 每次按下快速热键增加的速度 } } ], defaultSetting: { introDuration: 0, outroDuration: 0, forwardSeconds: 0, forwardKey: 'Space', playbackSpeedKeys: { slowDown: null, // 默认无慢速热键 reset: null, // 默认无重置速度热键 speedUp: null // 默认无快速热键 }, playbackSpeedSteps: { decreaseStep: 0.1, increaseStep: 0.1 } } }; // 用户自定义块结束 -------------------------------------------- // 获取当前页面对应的设置 function getSettingsForPage(userSettings) { let pageUrl = window.location.href; let pageTitle = document.title; // 获取页面标题 for (let setting of userSettings.settings) { if (new RegExp(setting.pattern).test(pageUrl) && new RegExp(setting.titlePattern).test(pageTitle)) { return setting; } } return userSettings.defaultSetting; } // 使用 MutationObserver 动态检测视频元素加载 function waitForVideoElement(callback) { const observer = new MutationObserver(() => { const video = document.querySelector('video'); if (video) { callback(video); observer.disconnect(); // 找到视频后停止观察 } }); observer.observe(document.body, { childList: true, subtree: true }); } // 应用跳过逻辑到视频元素上 function applySkipLogic(videoElement, settings) { let originalDuration = videoElement.duration; let userSeeking = false; videoElement.addEventListener('loadedmetadata', () => { originalDuration = videoElement.duration; }); videoElement.addEventListener('seeking', () => { userSeeking = true; }); videoElement.addEventListener('seeked', () => { userSeeking = false; }); videoElement.addEventListener('timeupdate', () => { if (!userSeeking && videoElement.currentTime < settings.introDuration) { videoElement.currentTime = settings.introDuration; } else if (!userSeeking && originalDuration - videoElement.currentTime < settings.outroDuration) { videoElement.currentTime = originalDuration - settings.outroDuration; } }); } // 监听键盘事件以实现快进 function setupKeyboardShortcut(settings) { document.addEventListener('keydown', event => { if (event.code === settings.forwardKey) { const video = document.querySelector('video'); if (video) { video.currentTime += settings.forwardSeconds; } } }); } // 处理播放速度控制的函数 function setupPlaybackSpeedControl(videoElement, settings) { let lastPlaybackRate = parseFloat(localStorage.getItem('lastPlaybackRate')) || 1.0; let hasReset = false; // 跟踪是否已经恢复过默认速度 let previousPlaybackRate = lastPlaybackRate; videoElement.playbackRate = lastPlaybackRate; function changePlaybackSpeed(event) { if (event.code === settings.playbackSpeedKeys.slowDown) { lastPlaybackRate -= settings.playbackSpeedSteps.decreaseStep; lastPlaybackRate = parseFloat(lastPlaybackRate.toFixed(2)); if (lastPlaybackRate < 0.1) { lastPlaybackRate = 0.1; } videoElement.playbackRate = lastPlaybackRate; localStorage.setItem('lastPlaybackRate', lastPlaybackRate); hasReset = false; // 更新播放速度时重置标志 } else if (event.code === settings.playbackSpeedKeys.speedUp) { lastPlaybackRate += settings.playbackSpeedSteps.increaseStep; lastPlaybackRate = parseFloat(lastPlaybackRate.toFixed(2)); videoElement.playbackRate = lastPlaybackRate; localStorage.setItem('lastPlaybackRate', lastPlaybackRate); hasReset = false; // 更新播放速度时重置标志 } else if (event.code === settings.playbackSpeedKeys.reset) { if (!hasReset) { // 第一次按下恢复默认速度并保存当前速度 previousPlaybackRate = lastPlaybackRate; videoElement.playbackRate = 1.0; lastPlaybackRate = 1.0; localStorage.setItem('lastPlaybackRate', lastPlaybackRate); hasReset = true; } else { // 再次按下恢复之前的播放速度 videoElement.playbackRate = previousPlaybackRate; lastPlaybackRate = previousPlaybackRate; localStorage.setItem('lastPlaybackRate', lastPlaybackRate); hasReset = false; } } } document.addEventListener('keydown', changePlaybackSpeed); } // 初始化 let settingsForPage = getSettingsForPage(userSettings); // 确保脚本在页面完全加载后执行,并等待视频元素加载完成 waitForVideoElement((video) => { if (settingsForPage !== userSettings.defaultSetting) { // 只有当URL和title都匹配时才应用逻辑 applySkipLogic(video, settingsForPage); setupKeyboardShortcut(settingsForPage); setupPlaybackSpeedControl(video, settingsForPage); } }); // 监听 video 元素的播放状态以恢复播放速度 function restorePlaybackSpeedOnNewVideo() { const observer = new MutationObserver(() => { const video = document.querySelector('video'); if (video) { let lastPlaybackRate = parseFloat(localStorage.getItem('lastPlaybackRate')) || 1.0; video.playbackRate = lastPlaybackRate; } }); observer.observe(document.body, { childList: true, subtree: true }); } restorePlaybackSpeedOnNewVideo(); // 在页面加载时启动恢复播放速度的监听 })(); // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see .